package edu.udo.sopra10.chaturaji.gui.util;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Insets;

/**
 * Improved LayoutManager based on {@link FlowLayout} which actually allows a
 * more dynamic flow with correct size updates when a line is wrapped.
 * 
 * @author Patrick Westerhoff
 */
public class WrappedFlowLayout extends FlowLayout
{
	private static final long serialVersionUID = 8777761711566760728L;
	private Dimension preferredLayoutSize;
	
	/**
	 * Constructs a WrappedFlowLayout with a centered alignment and a default
	 * 5-unit horizontal and vertical gap.
	 * 
	 */
	public WrappedFlowLayout ()
	{
		super();
	}
	
	/**
	 * Constructs a WrappedFlowLayout with the specified alignment and a
	 * default 5-unit gap.
	 * 
	 * The alignment argument must be one of the existing {@link FlowLayout}
	 * constants.
	 * 
	 * @param align the alignment value.
	 */
	public WrappedFlowLayout ( int align )
	{
		super( align );
	}
	
	/**
	 * Constructs a WrappedFlowLayout with the specified alignment, horizontal
	 * and vertical gap.
	 * 
	 * The alignment argument must be one of the existing {@link FlowLayout}
	 * constants.
	 * 
	 * @param align the alignment value.
	 * @param hgap horizontal gap.
	 * @param vgap vertical gap.
	 */
	public WrappedFlowLayout ( int align, int hgap, int vgap )
	{
		super( align, hgap, vgap );
	}
	
	/**
	 * Lays out the container. This method lets each visible component take its
	 * preferred size by reshaping the components in the target container in
	 * order to satisfy the alignment of this FlowLayout object.
	 * 
	 * @param target the specified component being laid out.
	 */
	@Override
	public void layoutContainer ( Container target )
	{
		Dimension size = preferredLayoutSize( target );
		
		if ( size.equals( preferredLayoutSize ) )
			super.layoutContainer( target );
		else
		{
			preferredLayoutSize = size;
			
			// revalidate most parent container
			Container parent = target;
			while ( parent.getParent() != null )
				parent = parent.getParent();
			parent.validate();
		}
	}
	
	@Override
	public Dimension preferredLayoutSize ( Container parent )
	{
		return layoutSize( parent, true );
	}
	
	@Override
	public Dimension minimumLayoutSize ( Container parent )
	{
		return layoutSize( parent, false );
	}
	
	/**
	 * Calculates the dimensions needed to layout the visible components
	 * contained in the specified target container.
	 * 
	 * @param parent the container that needs to be laid out.
	 * @param usePreferredSize if the preferred size should be calculated.
	 * @return the dimensions of the container.
	 */
	private Dimension layoutSize ( Container parent, boolean usePreferredSize )
	{
		synchronized ( parent.getTreeLock() )
		{
			// consider insets
			Insets insets = parent.getInsets();
			int horizontalInsets = insets.left + insets.right + 2 * getHgap();
			
			// target width
			int targetWidth = parent.getSize().width;
			if ( targetWidth == 0 )
				targetWidth = Short.MAX_VALUE;
			
			Dimension d = new Dimension( horizontalInsets, insets.top );
			int maxWidth = targetWidth - horizontalInsets;
			int rowWidth = 0;
			int rowHeight = 0;
			
			for ( Component component : parent.getComponents() )
			{
				if ( !component.isVisible() )
					continue;
				
				Dimension size = usePreferredSize ? component.getPreferredSize() : component.getMinimumSize();
				
				// not enough space in the current row
				if ( rowWidth + size.width > maxWidth )
				{
					d.width = Math.max( d.width, rowWidth );
					d.height += rowHeight + getVgap();
					
					// reset for next row
					rowWidth = rowHeight = 0;
				}
				
				// add horizontal gap between all components
				if ( rowWidth != 0 )
					rowWidth += getHgap();
				
				// update current row sizes
				rowWidth += size.width;
				rowHeight = Math.max( rowHeight, size.height );
			}
			
			// complete last row
			d.width = Math.max( d.width, rowWidth );
			d.height += rowHeight + getVgap() + insets.bottom;
			
			return d;
		}
	}
}